using System;
using System.Collections;
using System.Collections.Generic;
using System.IO.Ports;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Timers;
using CommunicationShared;

namespace Communication
{
    /// <summary>
    /// Interacts with a mobile phone at a low level to execute various functions.
    /// </summary>
    /// <remarks>This class also provides a second interface ICommunication that allows
    /// direct low level access to the connection.</remarks>
    public class GsmPhone : ICommunication
    {
        private readonly int _baudRate;
        private readonly AutoResetEvent _checkConnection;
        private const string CommError = "\r\nERROR\r\n";
        private const string CommOk = "\r\nOK\r\n";
        private Thread _commThread;
        private int _commThreadCheckDelay = 0x2710;
        private bool _connectionState;
        private readonly ManualResetEvent _dataReceived;
        private string _dataToSend;
        private readonly Queue _inputQueue;
        private bool _logEnabled;
        private readonly Queue _logQueue;
        private Thread _logThread;
        private const string MessageServiceErrorPattern = @"\r\n\+CMS ERROR: (\d+)\r\n";
        private const string MobileEquipmentErrorPattern = @"\r\n\+CME ERROR: (\d+)\r\n";
        private readonly ManualResetEvent _noData;
        private SerialPort _port;
        private readonly int _portNum;
        private readonly Queue _rawQueue;
        private readonly ManualResetEvent _receiveNow;
        private const int ReceiveTimeout = 0x1388;
        private readonly ManualResetEvent _sendDone;
        private readonly ManualResetEvent _sendNow;
        private readonly ManualResetEvent _terminateLogEvent;
        private readonly ManualResetEvent _terminateThread;
        private readonly int _timeout;

        /// <summary>
        /// The event that occurs when a new line was added to the log.
        /// </summary>
        public event LoglineAddedEventHandler LoglineAdded;

        /// <summary>
        /// The event that occurs when a new SMS message was received.
        /// </summary>
        public event MessageReceivedEventHandler MessageReceived;

        /// <summary>The event that occurs when the phone is connected.</summary>
        public event EventHandler PhoneConnected;

        /// <summary>The event that occurs when the phone is disconnected.</summary>
        public event EventHandler PhoneDisconnected;

        /// <summary>The event that occurs when receiving from the phone is completed.</summary>
        /// <remarks>This event is only fired by reading operations that may take longer to complete.</remarks>
        public event ProgressEventHandler ReceiveComplete;

        /// <summary>The event that occurs when new data was received from the phone.</summary>
        /// <remarks>This event is only fired by reading operations that may take longer to complete.</remarks>
        public event ProgressEventHandler ReceiveProgress;

        /// <summary>
        /// Initializing a new instance of the class.
        /// </summary>
        /// <param name="portNum">The communication (COM) port to use.</param>
        /// <param name="baudRate">The baud rate (speed) to use.</param>
        /// <param name="timeout">The communication timeout in milliseconds.</param>
        public GsmPhone(int portNum, int baudRate, int timeout)
        {
            _portNum = portNum;
            _baudRate = baudRate;
            _timeout = timeout;
            _logEnabled = true;
            _rawQueue = new Queue();
            _inputQueue = Queue.Synchronized(_rawQueue);
            _terminateThread = new ManualResetEvent(false);
            _dataReceived = new ManualResetEvent(false);
            _noData = new ManualResetEvent(false);
            _commThread = null;
            _dataToSend = string.Empty;
            _sendNow = new ManualResetEvent(false);
            _receiveNow = new ManualResetEvent(false);
            _sendDone = new ManualResetEvent(false);
            _connectionState = false;
            _checkConnection = new AutoResetEvent(false);
            _terminateLogEvent = new ManualResetEvent(false);
            _logQueue = new Queue();
            _logThread = null;
        }

        /// <summary>
        /// AT+CNMA. Confirms reception of a new message (SMS-DELIVER or SMS-STATUS-REPORT) which is routed
        /// directly to the TE. This acknowledgement command shall be used when "service" parameter
        /// of the GsmPhone.GetCurrentMessageService(System.Int32@,System.Int32@,System.Int32@,System.Int32@) function equals 1.
        /// </summary>
        /// <remarks>This sends a positive acknowledgement to the network.</remarks>
        public void AcknowledgeNewMessage()
        {
            lock (this)
            {
                AcknowledgeNewMessage(true);
            }
        }

        /// <summary>
        /// AT+CNMA. Confirms reception of a new message (SMS-DELIVER or SMS-STATUS-REPORT) which is routed
        /// directly to the TE.  This acknowledgement command shall be used when "service" parameter
        /// of the GsmPhone.GetCurrentMessageService(System.Int32@,System.Int32@,System.Int32@,System.Int32@) function equals 1.
        /// </summary>
        /// <param name="ok">Specifies whether the message was received correctly.
        /// Setting this parameter to true, will send a positive (RP-ACK) acknowledgement to the network.
        /// Setting this parameter to false, will send a negative (RP-ERROR) acknowledgement to the network.
        /// </param>
        /// <remarks>
        /// <para>If ME does not get acknowledgement within required time (network timeout), ME should send RP-ERROR to
        /// the network. ME/TA shall automatically disable routing to TE.</para>
        /// <para>If command is executed, but no acknowledgement is expected, or some other ME related error occurs,
        /// a MessageServiceErrorException is raised.</para>
        /// </remarks>
        public void AcknowledgeNewMessage(bool ok)
        {
            lock (this)
            {
                VerifyValidConnection();
                ActivatePduMode();
                string command = "AT+CNMA=" + (ok ? 0 : 2);
                ((ICommunication) this).ExecAndReceiveMultiple(command);
            }
        }

        /// <summary>
        /// AT+CMGF. Activates the PDU mode. Device must support it or the call will fail.
        /// </summary>
        private void ActivatePduMode()
        {
            LogIt("Activating PDU mode...");
            ((ICommunication) this).ExecAndReceiveMultiple("AT+CMGF=0");
        }

        /// <summary>
        /// AT+CMGF. Activates the text mode. Device must support it or the call will fail.
        /// </summary>
        private void ActivateTextMode()
        {
            LogIt("Activating text mode...");
            ((ICommunication) this).ExecAndReceiveMultiple("AT+CMGF=1");
        }

        private void AsyncCallback(IAsyncResult ar)
        {
            var result = (AsyncResult) ar;
            if (result.AsyncDelegate is MessageReceivedEventHandler)
            {
                this.LogIt("Ending async MessageReceivedEventHandler call");
                ((MessageReceivedEventHandler) result.AsyncDelegate).EndInvoke(ar);
            }
            else if (result.AsyncDelegate is EventHandler)
            {
                this.LogIt("Ending async EventHandler call");
                ((EventHandler) result.AsyncDelegate).EndInvoke(ar);
            }
            else if (result.AsyncDelegate is ProgressEventHandler)
            {
                ((ProgressEventHandler) result.AsyncDelegate).EndInvoke(ar);
            }
            else
            {
                LogIt("Warning: AsyncCallback got unknown delegate: " + result.AsyncDelegate.GetType().ToString());
            }
        }

        private void CheckConnection()
        {
            if (!Monitor.TryEnter(this))
            {
                LogIt("Object locked - connection check not performed.");
            }
            else
            {
                try
                {
                    bool flag = this.IsConnectedInternal();
                    if (flag != _connectionState)
                    {
                        _connectionState = flag;
                        if (_connectionState)
                        {
                            LogIt("Phone connected.");
                            OnPhoneConnected();
                        }
                        else
                        {
                            LogIt("Phone disconnected.");
                            OnPhoneDisconnected();
                        }
                    }
                }
                finally
                {
                    Monitor.Exit(this);
                }
            }
        }

        /// <summary>
        /// Closes the connection to the device.
        /// </summary>
        /// <remarks>You can check the current connection state with the <see cref="M:GsmComm.GsmCommunication.GsmPhone.IsOpen" /> method.
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.IsOpen" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.Open" />
        /// </remarks>
        /// <exception cref="T:System.InvalidOperationException">Port not open.</exception>
        public void Close()
        {
            lock (this)
            {
                if (!this.IsOpen())
                {
                    throw new InvalidOperationException("Port not open.");
                }
                this.TerminateCommThread();
                this.ClosePort();
                this.TerminateLogThread();
            }
        }

        private void ClosePort()
        {
            if (_port.IsOpen)
            {
                LogIt("Closing serial connection.");
                _port.Close();
                _port.DataReceived -= port_DataReceived;
                if (_connectionState)
                {
                    _connectionState = false;
                    OnPhoneDisconnected();
                }
            }
            else
            {
                this.LogIt("Attempted to close a closed serial connection. Ignored.");
            }
        }

        private void CommThread()
        {
            this.LogIt("Communication thread started.");
            System.Timers.Timer timer = new System.Timers.Timer(_commThreadCheckDelay);
            timer.Elapsed += connectionTimer_Elapsed;
            timer.AutoReset = false;
            timer.Start();
            LogIt("Connection to phone will be checked every {0} ms.", new object[] { _commThreadCheckDelay });
            WaitHandle[] waitHandles = new WaitHandle[] { _terminateThread, _sendNow, _receiveNow, _checkConnection };
            Label_008E:
            switch (WaitHandle.WaitAny(waitHandles))
            {
                case 0:
                    LogIt("Communication thread is terminating.");
                    if (!_terminateThread.WaitOne(0, false))
                        LogIt("Warning: Communication thread terminates without a stop signal!");
                    timer.Stop();
                    timer.Dispose();
                    _checkConnection.Reset();
                    _receiveNow.Reset();
                    _dataReceived.Reset();
                    _noData.Reset();
                    _inputQueue.Clear();
                    _sendNow.Reset();
                    _sendDone.Reset();
                    _dataToSend = string.Empty;
                    return;

                case 1:
                    {
                        string dataToSend = _dataToSend;
                        _sendNow.Reset();
                        _inputQueue.Clear();
                        _dataReceived.Reset();
                        SendInternal(dataToSend, true);
                        _sendDone.Set();
                        goto Label_008E;
                    }
                case 2:
                    if (CommThreadReceive())
                    {
                        timer.Stop();
                        _checkConnection.Reset();
                        timer.Start();
                    }
                    goto Label_008E;

                case 3:
                    timer.Stop();
                    CheckConnection();
                    timer.Start();
                    break;
            }
            goto Label_008E;
        }

        private bool CommThreadReceive()
        {
            bool flag2;
            _receiveNow.Reset();
            _noData.Reset();
            var handlers = new MessageIndicationHandlers();
            var builder = new StringBuilder();
            do
            {
                string str;
                flag2 = false;
                if (this.ReceiveInternal(out str))
                {
                    builder.Append(str);
                    flag2 = true;
                }
                try
                {
                    if (!flag2 && handlers.IsIncompleteUnsolicitedMessage(builder.ToString()))
                    {
                        this.LogIt("Incomplete unsolicited message found, reading on after sleep.");
                        Thread.Sleep(_timeout);
                        flag2 = true;
                    }
                }
                catch (Exception exception)
                {
                    this.LogIt("Error while checking received data for unsolicited messages.");
                    this.LogIt("Received data was:");
                    this.LogItShow(builder.ToString(), ">> ");
                    this.LogIt("This data will not be processed further.");
                    this.LogIt("Error details:");
                    this.LogItShow(exception.ToString(), "");
                    builder = new StringBuilder();
                    flag2 = false;
                }
            }
            while (flag2);
            if (builder.Length > 0)
            {
                _inputQueue.Enqueue(builder.ToString());
                string bigInput = MakeQueueString(_inputQueue);
                if (HandleUnsolicitedMessages(ref bigInput))
                {
                    _inputQueue.Clear();
                    _inputQueue.Enqueue(bigInput);
                }
                _dataReceived.Set();
                return true;
            }
            _noData.Set();
            return false;
        }

        private void connectionTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            _checkConnection.Set();
        }

        private void CreateCommThread()
        {
            if (IsCommThreadRunning())
                LogIt("Warning: Comm thread already created, ignoring call to CreateCommThread.");
            else
            {
                _terminateThread.Reset();
                _commThread = new Thread(CommThread) {Name = "GsmPhone comm thread"};
                _commThread.Start();
            }
        }

        private void CreateLogThread()
        {
            if ((_logThread != null) && _logThread.IsAlive)
            {
                LogIt("Warning: Logthread already created, ignoring call to CreateLogThread.");
            }
            else
            {
                _terminateLogEvent.Reset();
                _logThread = new Thread(new ThreadStart(this.LogThread));
                _logThread.Name = "GsmPhone log thread";
                _logThread.Start();
            }
        }

        /// <summary>
        /// Decodes a data stream with phonebook entries into <see cref="T:GsmComm.GsmCommunication.PhonebookEntry" /> objects.
        /// </summary>
        /// <param name="input">The entries to decode</param>
        /// <param name="prefix">The string the lines start with</param>
        private PhonebookEntry[] DecodePhonebookStream(string input, string prefix)
        {
            this.LogIt("Decoding phonebook entries...");
            ArrayList list = new ArrayList();
            Regex regex = new Regex(Regex.Escape(prefix) + "(\\d+),\"(.+)\",(\\d+),\"(.+)\".*\\r\\n");
            for (Match match = regex.Match(input); match.Success; match = match.NextMatch())
            {
                int index = int.Parse(match.Groups[1].Value);
                string number = match.Groups[2].Value;
                int type = int.Parse(match.Groups[3].Value);
                string text = match.Groups[4].Value;
                list.Add(new PhonebookEntry(index, number, type, text));
                this.LogIt("Entry: index=" + index.ToString() + ", number=\"" + number + "\", type=" + type.ToString() + ", text=\"" + text + "\"");
            }
            if (list.Count != 1)
            {
                this.LogIt(list.Count.ToString() + " entries decoded.");
            }
            else
            {
                this.LogIt("1 entry decoded.");
            }
            PhonebookEntry[] array = new PhonebookEntry[list.Count];
            list.CopyTo(array, 0);
            return array;
        }

        /// <summary>
        /// AT+CMGD. Deletes the specified SMS message from the current read/delete storage.
        /// </summary>
        /// <param name="index">The index of the message to delete.</param>
        public void DeleteMessage(int index)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Deleting message with index " + index.ToString() + "...");
                ((ICommunication) this).ExecAndReceiveMultiple("AT+CMGD=" + index.ToString());
            }
        }

        /// <summary>
        /// AT+CMGD. Deletes the specified SMS message from the current read/delete storage.
        /// </summary>
        /// <param name="index">The index of the message to delete.</param>
        /// <param name="delflag">The delete flag, this controls the behaviour of the delete command.</param>
        public void DeleteMessage(int index, DeleteFlag delflag)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Deleting message with index " + index.ToString() + ", using delflag " + delflag.ToString() + "...");
                string command = "AT+CMGD=" + index.ToString() + "," + ((int) delflag).ToString();
                ((ICommunication) this).ExecAndReceiveMultiple(command);
            }
        }

        /// <summary>
        /// AT+CPBW. Deletes a phonebook entry.
        /// </summary>
        /// <param name="index">The index of the entry to delete.</param>
        /// <remarks>In this case it does not matter whether the specified index is valid.
        /// If the entry does not exist, no error is returned.</remarks>
        public void DeletePhonebookEntry(int index)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Deleting phonebook entry " + index.ToString() + "...");
                ((ICommunication) this).ExecAndReceiveMultiple("AT+CPBW=" + index.ToString());
            }
        }

        private bool DispatchLog()
        {
            string text = string.Empty;
            lock (_logQueue)
            {
                if (_logQueue.Count > 0)
                {
                    text = (string) _logQueue.Dequeue();
                }
            }
            if (text.Length <= 0)
            {
                return false;
            }
            if (this.LoglineAdded != null)
            {
                this.LoglineAdded(this, text);
            }
            return true;
        }

        /// <summary>
        /// Enters a password at the phone which is necessary before it can operated.
        /// </summary>
        /// <param name="pin">The SIM PIN, SIM PUK or other password required.</param>
        /// <remarks>Get the current PIN status with <see cref="M:GsmComm.GsmCommunication.GsmPhone.GetPinStatus" /> to check
        /// whether a password must be entered.</remarks>
        public void EnterPin(string pin)
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Entering PIN...", new object[] { pin });
                _logEnabled = false;
                try
                {
                    string command = string.Format("AT+CPIN=\"{0}\"", pin);
                    ((ICommunication) this).ExecAndReceiveMultiple(command);
                }
                finally
                {
                    _logEnabled = true;
                }
            }
        }

        private string ExecCommandInternal(string command, string receiveErrorMessage)
        {
            string output = command + "\r";
            _receiveNow.Reset();
            SendInternal(output, false);
            StringBuilder builder = new StringBuilder();
            while (_receiveNow.WaitOne(_timeout, false))
            {
                string str2;
                _receiveNow.Reset();
                ReceiveInternal(out str2);
                builder.Append(str2);
            }
            string input = builder.ToString();
            if (input.Length == 0)
            {
                HandleRecvError(input, receiveErrorMessage);
                return null;
            }
            if (!IsSuccess(input))
            {
                HandleCommError(input);
                return null;
            }
            if (input.EndsWith("\r\nOK\r\n"))
            {
                input = input.Remove(input.LastIndexOf("\r\nOK\r\n"), "\r\nOK\r\n".Length);
            }
            if (input.StartsWith(output))
            {
                input = input.Substring(output.Length);
            }
            return input;
        }

        /// <summary>
        /// AT+CPBF. Searches for the specified text in the phonebook.
        /// </summary>
        /// <param name="findText">The text to find.</param>
        /// <returns>An array of <see cref="T:GsmComm.GsmCommunication.PhonebookEntry" /> objects containing
        /// the specified text</returns>
        public PhonebookEntry[] FindPhonebookEntries(string findText)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Searching phonebook entry \"" + findText + "\"...");
                string command = "AT+CPBF=\"" + findText + "\"";
                string input = ((ICommunication) this).ExecAndReceiveMultiple(command);
                return this.DecodePhonebookStream(input, "+CPBF: ");
            }
        }

        /// <summary>
        /// AT+CBC. Gets the ME battery charging status and charge level.
        /// </summary>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.BatteryChargeInfo" /> object containing the battery details.</returns>
        public BatteryChargeInfo GetBatteryCharge()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting battery charge...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CBC");
                Match match = new Regex(@"\+CBC: (\d+),(\d+)").Match(input);
                if (match.Success)
                {
                    int batteryChargingStatus = int.Parse(match.Groups[1].Value);
                    int batteryChargeLevel = int.Parse(match.Groups[2].Value);
                    this.LogIt("Battery charging status = " + batteryChargingStatus.ToString());
                    this.LogIt("Battery charge level = " + batteryChargeLevel.ToString());
                    return new BatteryChargeInfo(batteryChargingStatus, batteryChargeLevel);
                }
                this.HandleCommError(input);
                return null;
            }
        }

        /// <summary>
        /// AT+CSCS. Retrives the currently selected character set.
        /// </summary>
        /// <returns>The current character set.</returns>
        /// <remarks>
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.SelectCharacterSet(System.String)" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.GetSupportedCharacterSets" />
        /// </remarks>
        public string GetCurrentCharacterSet()
        {
            lock (this)
            {
                string str2;
                this.VerifyValidConnection();
                this.LogIt("Retrieving current character set...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CSCS?");
                Match match = new Regex("\\+CSCS: \"([^\"]+)\"").Match(input);
                if (match.Success)
                {
                    str2 = match.Groups[1].Value;
                }
                else
                {
                    str2 = string.Empty;
                    this.HandleCommError(input);
                }
                this.LogIt("Current character set is \"" + str2 + "\".");
                return str2;
            }
        }

        /// <summary>
        /// AT+CSMS. Gets the supported message types along with the current service setting.
        /// </summary>
        /// <param name="service">Specifies the compatibility level of the SMS AT commands.
        /// The requirement of service setting 1 depends on specific commands.
        /// </param>
        /// <param name="mt">ME supports mobile terminated messages</param>
        /// <param name="mo">ME supports mobile originated messages</param>
        /// <param name="bm">ME supports broadcast type messages</param>
        public void GetCurrentMessageService(out int service, out int mt, out int mo, out int bm)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CSMS?");
                Match match = new Regex(@"\+CSMS: (\d+),(\d+),(\d+),(\d+)").Match(input);
                if (!match.Success)
                {
                    this.HandleCommError(input);
                    throw new CommException("Unexpected response.", input);
                }
                service = int.Parse(match.Groups[1].Value);
                mt = int.Parse(match.Groups[2].Value);
                mo = int.Parse(match.Groups[3].Value);
                bm = int.Parse(match.Groups[4].Value);
            }
        }

        /// <summary>
        /// AT+COPS. Gets the currently selected network operator.
        /// </summary>
        /// <returns>An <see cref="T:GsmComm.GsmCommunication.OperatorInfo" /> object containing the data or null if there is no current operator.</returns>
        public OperatorInfo GetCurrentOperator()
        {
            OperatorInfo info2;
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting current operator...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+COPS?");
                Match match = new Regex("\\+COPS: (\\d+)(?:,(\\d+),\"(.+)\")?(?:,(.+))?").Match(input);
                if (match.Success)
                {
                    int.Parse(match.Groups[1].Value);
                    if (match.Groups.Count > 1)
                    {
                        int num = int.Parse(match.Groups[2].Value);
                        string theOperator = match.Groups[3].Value;
                        string accessTechnology = string.Empty;
                        if (match.Groups.Count > 3)
                        {
                            accessTechnology = match.Groups[4].Value;
                        }
                        this.LogIt("format={0}, oper=\"{1}\", act=\"{2}\"", new object[] { num, theOperator, accessTechnology });
                        if (!Enum.IsDefined(typeof(OperatorFormat), num))
                        {
                            throw new CommException("Unknown operator format " + num.ToString(), input);
                        }
                        return new OperatorInfo((OperatorFormat) Enum.Parse(typeof(OperatorFormat), num.ToString()), theOperator, accessTechnology);
                    }
                    this.LogIt("There is no operator currently selected!");
                    return null;
                }
                this.HandleCommError(input);
                throw new CommException("Unexpected response.", input);
            }
            return info2;
        }

        /// <summary>
        /// AT+CNMI. Gets the current message notification settings.
        /// </summary>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.MessageIndicationSettings" /> structure containing the detailed settings.</returns>
        public MessageIndicationSettings GetMessageIndications()
        {
            MessageIndicationSettings settings2;
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting current message indications...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CNMI?");
                Match match = new Regex(@"\+CNMI: (\d+),(\d+),(\d+),(\d+),(\d+)").Match(input);
                if (match.Success)
                {
                    MessageIndicationSettings settings = new MessageIndicationSettings();
                    settings.Mode = int.Parse(match.Groups[1].Value);
                    settings.DeliverStyle = int.Parse(match.Groups[2].Value);
                    settings.CellBroadcastStyle = int.Parse(match.Groups[3].Value);
                    settings.StatusReportStyle = int.Parse(match.Groups[4].Value);
                    settings.BufferSetting = int.Parse(match.Groups[5].Value);
                    this.LogIt(string.Format("mode={0:g}, mt={1:g}, bm={2:g}, ds={3:g}, bfr={4:g}", new object[] { settings.Mode, settings.DeliverStyle, settings.CellBroadcastStyle, settings.StatusReportStyle, settings.BufferSetting }));
                    settings2 = settings;
                }
                else
                {
                    this.HandleCommError(input);
                    throw new CommException("Unexpected response.", input);
                }
            }
            return settings2;
        }

        /// <summary>
        /// Returns the message service error code in the input string.
        /// </summary>
        /// <param name="input">The data received</param>
        /// <returns>The error code</returns>
        /// <remarks>Use the <see cref="M:GsmComm.GsmCommunication.GsmPhone.IsMessageServiceError(System.String)" /> method to check if the string
        /// contains a message service error message.</remarks>
        /// <exception cref="T:System.ArgumentException">Input string does not contain a message service error code</exception>
        private int GetMessageServiceErrorCode(string input)
        {
            Match match = new Regex(@"\r\n\+CMS ERROR: (\d+)\r\n").Match(input);
            if (!match.Success || (match.Groups[1].Captures.Count <= 0))
            {
                throw new ArgumentException("The input string does not contain a message service error code.");
            }
            return int.Parse(match.Groups[1].Captures[0].ToString());
        }

        /// <summary>
        /// AT+CPMS. Gets the supported message storages.
        /// </summary>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.MessageStorageInfo" /> object that contains details about the supported storages.</returns>
        public MessageStorageInfo GetMessageStorages()
        {
            lock (this)
            {
                int num2;
                MessageStorageInfo info;
                this.VerifyValidConnection();
                ArrayList list = new ArrayList();
                this.LogIt("Enumerating supported message storages...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CPMS=?");
                int index = input.IndexOf("+CPMS: ");
                if (index >= 0)
                {
                    input = input.Substring(index + "+CPMS: ".Length);
                }
                else
                {
                    this.HandleCommError(input);
                }
                Match match = new Regex("\\((?:\"(\\w+)\"(?(?!\\)),))+\\)").Match(input);
                if (match.Success)
                {
                    goto Label_00E9;
                }
                throw new CommException("Invalid response format. The response started correctly, but there is an error in the remaining part.", input);
                Label_0081:
                num2 = match.Groups[1].Captures.Count;
                string[] strArray = new string[num2];
                for (int i = 0; i < num2; i++)
                {
                    strArray[i] = match.Groups[1].Captures[i].Value;
                }
                list.Add(strArray);
                match = match.NextMatch();
                Label_00E9:
                if (match.Success)
                {
                    goto Label_0081;
                }
                int count = list.Count;
                this.LogIt(count.ToString() + " storage groups received.");
                if (count < 2)
                {
                    this.LogIt("Error: Expected at least 2 storage groups, only " + count.ToString() + " received.");
                    throw new CommException("Not all all storage groups received, only " + list.Count.ToString() + ", expected at least 2.");
                }
                info.ReadStorages = (string[]) list[0];
                info.WriteStorages = (string[]) list[1];
                if (count > 2)
                {
                    info.ReceiveStorages = (string[]) list[2];
                }
                else
                {
                    info.ReceiveStorages = new string[0];
                }
                string str2 = MakeArrayString(info.ReadStorages);
                string str3 = MakeArrayString(info.WriteStorages);
                string str4 = MakeArrayString(info.ReceiveStorages);
                this.LogIt("Supported read storages: " + str2);
                this.LogIt("Supported write storage: " + str3);
                this.LogIt("Supported receive storages: " + str4);
                return info;
            }
        }

        /// <summary>
        /// Returns the mobile equipment error code in the input string.
        /// </summary>
        /// <param name="input">The data received</param>
        /// <returns>The mobile equipment error code</returns>
        /// <remarks>Use the <see cref="M:GsmComm.GsmCommunication.GsmPhone.IsMobileEquipmentError(System.String)" /> method to check if the string
        /// contains mobile equipment error message.</remarks>
        /// <exception cref="T:System.ArgumentException">Input string does not contain a mobile equipment error code</exception>
        private int GetMobileEquipmentErrorCode(string input)
        {
            Match match = new Regex(@"\r\n\+CME ERROR: (\d+)\r\n").Match(input);
            if (!match.Success || (match.Groups[1].Captures.Count <= 0))
            {
                throw new ArgumentException("The input string does not contain a mobile equipment error code.");
            }
            return int.Parse(match.Groups[1].Captures[0].ToString());
        }

        /// <summary>
        /// AT+CMMS. Gets the current SMS batch mode setting.
        /// </summary>
        /// <returns>The current mode.</returns>
        public MoreMessagesMode GetMoreMessagesToSend()
        {
            MoreMessagesMode mode2;
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting more messages mode...");
                string command = string.Format("AT+CMMS?", new object[0]);
                string input = ((ICommunication) this).ExecAndReceiveMultiple(command);
                Match match = new Regex(@"\+CMMS: (\d+)").Match(input);
                if (match.Success)
                {
                    int num = int.Parse(match.Groups[1].Value);
                    this.LogIt("mode=\"{0}\"", new object[] { num });
                    if (!Enum.IsDefined(typeof(MoreMessagesMode), num))
                    {
                        throw new CommException("Unknown more messages mode " + num.ToString() + ".", input);
                    }
                    return (MoreMessagesMode) Enum.Parse(typeof(MoreMessagesMode), num.ToString());
                }
                this.HandleCommError(input);
                throw new CommException("Unexpected response.", input);
            }
            return mode2;
        }

        /// <summary>
        /// AT+COPS. Determines the current mode to select a network operator.
        /// </summary>
        /// <returns>The current mode, see OperatorSelectionMode for possible values.</returns>
        public int GetOperatorSelectionMode()
        {
            int num2;
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Getting current operator selection mode...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+COPS?");
                Match match = new Regex("\\+COPS: (\\d+)(?:,(\\d+),\"(.+)\")?(?:,(.+))?").Match(input);
                if (match.Success)
                {
                    int num = int.Parse(match.Groups[1].Value);
                    LogIt("Current mode is {0}.", new object[] { num.ToString() });
                    num2 = num;
                }
                else
                {
                    HandleCommError(input);
                    throw new CommException("Unexpected response.", input);
                }
            }
            return num2;
        }

        /// <summary>
        /// AT+CPBS. Gets the memory status of the currently selected phonebook storage.
        /// </summary>
        /// <returns>The memory status of the currently selected storage.</returns>
        public MemoryStatusWithStorage GetPhonebookMemoryStatus()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting phonebook memory status...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CPBS?");
                MemoryStatusWithStorage storage = this.ParsePhonebookMemoryStatus(input);
                int num = (storage.Total > 0) ? ((int) ((((double) storage.Used) / ((double) storage.Total)) * 100.0)) : 0;
                this.LogIt(string.Format("Memory status: storage=\"{0}\" {1}/{2} ({3}% used)", new object[] { storage.Storage, storage.Used, storage.Total, num }));
                return storage;
            }
        }

        /// <summary>
        /// AT+CPBR. Queries the size of the currently selected phonebook.
        /// </summary>
        /// <param name="lowerBound">Receives the lower bound of the phonebook</param>
        /// <param name="upperBound">Receives the upper bound of the phonebook</param>
        /// <param name="nLength">Receives the maximum number length, 0 if unknown</param>
        /// <param name="tLength">Receives the maximum text length, 0 if unknown</param>
        public void GetPhonebookSize(out int lowerBound, out int upperBound, out int nLength, out int tLength)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting phonebook size...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CPBR=?");
                Match match = new Regex(@"\+CPBR: \((\d+)-(\d+)\)\,(\d*),(\d*)").Match(input);
                if (!match.Success)
                {
                    this.HandleCommError(input);
                }
                lowerBound = int.Parse(match.Groups[1].Value);
                upperBound = int.Parse(match.Groups[2].Value);
                nLength = (match.Groups[3].Value != "") ? int.Parse(match.Groups[3].Value) : 0;
                tLength = (match.Groups[4].Value != "") ? int.Parse(match.Groups[4].Value) : 0;
                this.LogIt("lowerBound=" + ((int) lowerBound).ToString() + ", upperBound=" + ((int) upperBound).ToString() + ", nLength=" + ((int) nLength).ToString() + ", tLength=" + ((int) tLength).ToString());
            }
        }

        /// <summary>
        /// AT+CPBS. Gets the supported phonebook storages.
        /// </summary>
        /// <returns>An array of the supported storages</returns>
        public string[] GetPhonebookStorages()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Enumerating supported storages...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CPBS=?");
                string[] strArray = null;
                Match match = new Regex("\\+CPBS: \\((?:\"(\\w+)\"(?(?!\\)),))+\\)").Match(input);
                if (match.Success)
                {
                    int count = match.Groups[1].Captures.Count;
                    strArray = new string[count];
                    for (int j = 0; j < count; j++)
                    {
                        strArray[j] = match.Groups[1].Captures[j].Value;
                    }
                }
                else
                {
                    this.HandleCommError(input);
                }
                string str2 = string.Empty;
                for (int i = 0; i < strArray.Length; i++)
                {
                    str2 = str2 + ((str2 == string.Empty) ? "" : ", ") + strArray[i];
                }
                this.LogIt("Supported storages: " + str2);
                return strArray;
            }
        }

        /// <summary>
        /// Returns a value indicating whether some password must be entered at the phone or not.
        /// </summary>
        /// <returns>The current PIN status as one of the <see cref="T:GsmComm.GsmCommunication.PinStatus" /> values.</returns>
        public PinStatus GetPinStatus()
        {
            PinStatus status;
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting PIN status...");
                string command = string.Format("AT+CPIN?", new object[0]);
                string input = ((ICommunication) this).ExecAndReceiveMultiple(command);
                Match match = new Regex(@"\+CPIN: (.+)\r\n").Match(input);
                if (match.Success)
                {
                    string s = match.Groups[1].Value;
                    this.LogIt("status=\"{0}\"", new object[] { s });
                    status = MapPinStatusStringToStatus(s);
                }
                else
                {
                    this.HandleCommError(input);
                    throw new CommException("Unexpected response.", input);
                }
            }
            return status;
        }

        /// <summary>
        /// AT+CSQ. Gets the signal quality as calculated by the ME.
        /// </summary>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.SignalQualityInfo" /> object containing the signal details.</returns>
        public SignalQualityInfo GetSignalQuality()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting signal quality...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CSQ");
                Match match = new Regex(@"\+CSQ: (\d+),(\d+)").Match(input);
                if (match.Success)
                {
                    int signalStrength = int.Parse(match.Groups[1].Value);
                    int bitErrorRate = int.Parse(match.Groups[2].Value);
                    this.LogIt("Signal strength = " + signalStrength.ToString());
                    this.LogIt("Bit error rate = " + bitErrorRate.ToString());
                    return new SignalQualityInfo(signalStrength, bitErrorRate);
                }
                this.HandleCommError(input);
                return null;
            }
        }

        /// <summary>
        /// AT+CSCA. Gets the SMS Service Center Address.
        /// </summary>
        /// <returns>The current SMSC address</returns>
        /// <remarks>This command returns the SMSC address, through which SMS messages are transmitted.
        /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is
        /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals
        /// zero.</remarks>
        public AddressData GetSmscAddress()
        {
            AddressData data2;
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting SMSC address...");
                string command = "AT+CSCA?";
                string input = ((ICommunication) this).ExecAndReceiveMultiple(command);
                Match match = new Regex("\\+CSCA: \"(.*)\",(\\d+)").Match(input);
                if (match.Success)
                {
                    string address = match.Groups[1].Value;
                    int typeOfAddress = int.Parse(match.Groups[2].Value);
                    this.LogIt("address=\"{0}\", typeOfAddress={1}", new object[] { address, typeOfAddress.ToString() });
                    AddressData data = new AddressData(address, typeOfAddress);
                    data2 = data;
                }
                else
                {
                    this.HandleCommError(input);
                    throw new CommException("Unexpected response.", input);
                }
            }
            return data2;
        }

        /// <summary>
        /// AT+CNUM. Returns the MSISDNs related to the subscriber.
        /// </summary>
        /// <returns>An array of <see cref="T:GsmComm.GsmCommunication.SubscriberInfo" /> objects with one for each MSISDN
        /// (Mobile Subscriber ISDN Number), depending on the services subscribed.</returns>
        /// <remarks>
        /// <para>This information can be stored in the SIM/UICC or in the MT.</para>
        /// <para>If the command is supported by the phone but no number can be retrieved,
        /// an empty array is returned.</para>
        /// </remarks>
        public SubscriberInfo[] GetSubscriberNumbers()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting subscriber numbers...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CNUM");
                Match match = new Regex("\\+CNUM: (.*),\"(.*)\",(\\d+)(?:,(\\d+),(\\d+)(?:,(\\d+))?)?(?:\\r\\n)?").Match(input);
                List<SubscriberInfo> list = new List<SubscriberInfo>();
                while (match.Success)
                {
                    string alpha = ((match.Groups[1].Captures.Count > 0) && (match.Groups[1].Length > 0)) ? match.Groups[1].Value : string.Empty;
                    string number = match.Groups[2].Value;
                    int type = int.Parse(match.Groups[3].Value);
                    int speed = ((match.Groups[4].Captures.Count > 0) && (match.Groups[4].Length > 0)) ? int.Parse(match.Groups[4].Value) : -1;
                    int service = ((match.Groups[5].Captures.Count > 0) && (match.Groups[5].Length > 0)) ? int.Parse(match.Groups[5].Value) : -1;
                    int itc = ((match.Groups[6].Captures.Count > 0) && (match.Groups[6].Length > 0)) ? int.Parse(match.Groups[6].Value) : -1;
                    this.LogIt("alpha=\"{0}\",number=\"{1}\",type={2},speed={3},service={4},itc={5}", new object[] { alpha, number, type, speed, service, itc });
                    SubscriberInfo item = new SubscriberInfo(alpha, number, type, speed, service, itc);
                    list.Add(item);
                    match = match.NextMatch();
                }
                SubscriberInfo[] array = new SubscriberInfo[list.Count];
                list.CopyTo(array);
                return array;
            }
        }

        /// <summary>
        /// AT+CSCS. Retrieves the phone's supported character sets.
        /// </summary>
        /// <returns>A string array containing the supported character sets.</returns>
        /// <remarks>
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.SelectCharacterSet(System.String)" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.GetCurrentCharacterSet" />
        /// </remarks>
        public string[] GetSupportedCharacterSets()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Enumerating supported character sets...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CSCS=?");
                string[] array = null;
                Match match = new Regex("\\+CSCS: \\((?:\"([^\"]+)\"(?(?!\\)),))+\\)").Match(input);
                if (match.Success)
                {
                    int count = match.Groups[1].Captures.Count;
                    array = new string[count];
                    for (int i = 0; i < count; i++)
                    {
                        array[i] = match.Groups[1].Captures[i].Value;
                    }
                }
                else
                {
                    this.HandleCommError(input);
                }
                this.LogIt("Supported character sets: " + MakeArrayString(array));
                return array;
            }
        }

        /// <summary>
        /// AT+CNMI. Gets the supported new message indications from the phone.
        /// </summary>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.MessageIndicationSupport" /> object containing information about the supported
        /// indications.</returns>
        public MessageIndicationSupport GetSupportedIndications()
        {
            MessageIndicationSupport support2;
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Getting supported message indications...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CNMI=?");
                Match match = new Regex(@"\+CNMI: \(([\d,-])+\),\(([\d,-]+)\),\(([\d,-]+)\),\(([\d,-]+)\),\(([\d,-]+)\)").Match(input);
                if (match.Success)
                {
                    string mode = string.Empty;
                    string deliver = string.Empty;
                    string cellBroadcast = string.Empty;
                    string statusReport = string.Empty;
                    string buffer = string.Empty;
                    foreach (Capture capture in match.Groups[1].Captures)
                    {
                        mode = mode + capture.Value;
                    }
                    foreach (Capture capture2 in match.Groups[2].Captures)
                    {
                        deliver = deliver + capture2.Value;
                    }
                    foreach (Capture capture3 in match.Groups[3].Captures)
                    {
                        cellBroadcast = cellBroadcast + capture3.Value;
                    }
                    foreach (Capture capture4 in match.Groups[4].Captures)
                    {
                        statusReport = statusReport + capture4.Value;
                    }
                    foreach (Capture capture5 in match.Groups[5].Captures)
                    {
                        buffer = buffer + capture5.Value;
                    }
                    this.LogIt("mode=\"{0}\", deliver=\"{1}\", cellBroadcast=\"{2}\", statusReport=\"{3}\", buffer=\"{4}\"", new object[] { mode, deliver, cellBroadcast, statusReport, buffer });
                    return new MessageIndicationSupport(mode, deliver, cellBroadcast, statusReport, buffer);
                }
                this.HandleCommError(input);
                throw new CommException("Unexpected response.", input);
            }
            return support2;
        }

        string ICommunication.ExecAndReceiveAnything(string command, string pattern)
        {
            string output = command + "\r";
            ((ICommunication) this).Send(output);
            string str2 = ((ICommunication) this).ReceiveMultiple(pattern);
            if (str2.StartsWith(output))
            {
                str2 = str2.Substring(output.Length);
            }
            return str2;
        }

        /// <summary>
        /// Executes the specified command and waits for multiple responses.
        /// </summary>
        /// <param name="command">The command to send, do not include termination characters.</param>
        /// <returns>The received response.</returns>
        /// <remarks>
        /// <para>Use this method if the response from the device is expected to be larger
        /// than one response from the device or when in doubt about the response length.</para>
        /// <para>The sequence is the following:</para>
        /// <para>- Appends CR to output and sends that string</para>
        /// <para>- Waits for multiple responses until an ok or error message is received
        /// or receiving is aborted because of too many empty retries.</para>
        /// <para>- Strips away ok code if it's at the end</para>
        /// <para>- Strips away output if it's mirrored at the beginning</para>
        /// <para>- return input if successful, otherwise an exception is thrown</para>
        /// </remarks>
        string ICommunication.ExecAndReceiveMultiple(string command)
        {
            string output = command + "\r";
            ((ICommunication) this).Send(output);
            string str2 = ((ICommunication) this).ReceiveMultiple();
            if (str2.EndsWith("\r\nOK\r\n"))
            {
                str2 = str2.Remove(str2.LastIndexOf("\r\nOK\r\n"), "\r\nOK\r\n".Length);
            }
            if (str2.StartsWith(output))
            {
                str2 = str2.Substring(output.Length);
            }
            return str2;
        }

        /// <summary>
        /// Executes the specified command and waits for a single response.
        /// </summary>
        /// <param name="command">The command to send, do not include termination characters.</param>
        /// <returns>The received response.</returns>
        /// <remarks>
        /// <para>The sequence is the following:</para>
        /// <para>- Appends CR to output and sends that string</para>
        /// <para>- Waits for a single response</para>
        /// <para>- Strips away ok code if it's at the end</para>
        /// <para>- Strips away output if it's mirrored at the beginning</para>
        /// <para>- return input if successful, otherwise an exception is thrown</para>
        /// </remarks>
        string ICommunication.ExecCommand(string command)
        {
            return ((ICommunication) this).ExecCommand(command, "No answer from phone.");
        }

        /// <summary>
        /// Executes the specified command and waits for a single response.
        /// </summary>
        /// <param name="command">The command to send, do not include termination characters.</param>
        /// <param name="receiveErrorMessage">The string to include in the exception message if
        /// there is an error while waiting for the response.</param>
        /// <returns>The received response.</returns>
        /// <remarks>
        /// <para>The sequence is the following:</para>
        /// <para>- Appends CR to output and sends that string</para>
        /// <para>- Waits for a single response</para>
        /// <para>- Strips away ok code if it's at the end</para>
        /// <para>- Strips away output if it's mirrored at the beginning</para>
        /// <para>- return input if successful, otherwise an exception is thrown</para>
        /// </remarks>
        string ICommunication.ExecCommand(string command, string receiveErrorMessage)
        {
            string str2;
            string output = command + "\r";
            ((ICommunication) this).Send(output);
            if (!((ICommunication) this).Receive(out str2))
            {
                HandleRecvError(str2, receiveErrorMessage);
                return null;
            }
            LogItShow(str2, ">> ");
            if (!IsSuccess(str2))
            {
                HandleCommError(str2);
                return null;
            }
            if (str2.EndsWith("\r\nOK\r\n"))
            {
                str2 = str2.Remove(str2.LastIndexOf("\r\nOK\r\n"), "\r\nOK\r\n".Length);
            }
            if (str2.StartsWith(output))
            {
                str2 = str2.Substring(output.Length);
            }
            return str2;
        }

        bool ICommunication.Receive(out string input)
        {
            input = string.Empty;
            if (!this.IsCommThreadRunning())
            {
                throw new InvalidOperationException("Communication thread is not running.");
            }
            WaitHandle[] waitHandles = new WaitHandle[] { _dataReceived, _noData };
            if (WaitHandle.WaitAny(waitHandles, 0x1388, false) == 0)
            {
                _dataReceived.Reset();
                if (_inputQueue.Count > 0)
                {
                    while (_inputQueue.Count > 0)
                    {
                        input = input + ((string) _inputQueue.Dequeue());
                    }
                }
                else
                {
                    LogIt("Warning: Nothing in input queue");
                }
            }
            _noData.Reset();
            return (input.Length > 0);
        }

        /// <summary>
        /// Waits for multiple responses.
        /// </summary>
        /// <returns>The response received.</returns>
        string ICommunication.ReceiveMultiple()
        {
            return ((ICommunication) this).ReceiveMultiple(string.Empty);
        }

        /// <summary>
        /// Waits for multiple responses.
        /// </summary>
        /// <param name="pattern">Specify the pattern that the received data must match to stop reading,
        /// can be an empty string if the pattern should not be checked.</param>
        /// <returns>The response received.</returns>
        string ICommunication.ReceiveMultiple(string pattern)
        {
            string input = string.Empty;
            int tickCount = Environment.TickCount;
            this.OnReceiveProgress(input.Length);
            int num2 = 0;
            int num3 = 0;
            while ((!this.IsSuccess(input) && !this.IsCommError(input)) && ((!this.IsMessageServiceError(input) && !this.IsMobileEquipmentError(input)) && (num2 < 6)))
            {
                string str2;
                if (((ICommunication) this).Receive(out str2))
                {
                    input = input + str2;
                    num2 = 0;
                    num3++;
                    this.OnReceiveProgress(input.Length);
                    if ((pattern.Length <= 0) || !Regex.IsMatch(input, pattern))
                    {
                        continue;
                    }
                    break;
                }
                num2++;
                if (num2 > 1)
                {
                    this.LogIt("Waiting for response from phone (" + num2.ToString() + " empty read(s)).");
                }
            }
            int num4 = Environment.TickCount - tickCount;
            this.LogItShow(input, ">> ");
            this.OnReceiveComplete(input.Length);
            if (num2 >= 6)
            {
                this.LogIt("Timeout after " + num2.ToString() + " empty reading attempts within " + num4.ToString() + " ms.");
                throw new CommException("No data received from phone after waiting for " + num4.ToString() + " ms.");
            }
            if (num4 > _timeout)
            {
                this.LogIt("{0} characters received after {1} ms.", new object[] { input.Length.ToString(), num4.ToString() });
            }
            if ((pattern.Length == 0) && !this.IsSuccess(input))
            {
                this.HandleCommError(input);
                return string.Empty;
            }
            this.HandleUnsolicitedMessages(ref input);
            return input;
        }

        /// <summary>
        /// Sends raw data as a string.
        /// </summary>
        /// <param name="output">The data to send.</param>
        /// <remarks>Send and receive buffers are cleared before sending.</remarks>
        void ICommunication.Send(string output)
        {
            if (!this.IsCommThreadRunning())
            {
                throw new InvalidOperationException("Communication thread is not running.");
            }
            _dataToSend = output;
            _sendDone.Reset();
            _sendNow.Set();
            _sendDone.WaitOne();
        }

        /// <summary>
        /// Handles a communication error.
        /// </summary>
        /// <param name="input">The data received.</param>
        /// <remarks>Call this function when communicating and the response is not a success response. This
        /// function checks if the response contains an error message. In any case a <see cref="T:GsmComm.GsmCommunication.CommException" />
        /// or an exception derived from it is thrown based on the type of error.
        /// </remarks>
        /// <exception cref="T:GsmComm.GsmCommunication.CommException">Always thrown based on type of error.</exception>
        private void HandleCommError(string input)
        {
            if (this.IsCommError(input))
            {
                this.LogIt("Failed. Phone reports generic communication error or syntax error:");
                this.LogItShow(input, "");
                throw new CommException("Phone reports generic communication error or syntax error.", input);
            }
            if (this.IsMessageServiceError(input))
            {
                int messageServiceErrorCode = this.GetMessageServiceErrorCode(input);
                this.LogIt("Failed. Phone reports message service (MS) error " + messageServiceErrorCode.ToString());
                this.LogItShow(input, "");
                throw new MessageServiceErrorException("Message service error " + messageServiceErrorCode.ToString(), messageServiceErrorCode, input);
            }
            if (this.IsMobileEquipmentError(input))
            {
                int mobileEquipmentErrorCode = this.GetMobileEquipmentErrorCode(input);
                this.LogIt("Failed. Phone reports mobile equipment (ME) error " + mobileEquipmentErrorCode.ToString());
                this.LogItShow(input, "");
                throw new MobileEquipmentErrorException("Mobile equipment error " + mobileEquipmentErrorCode.ToString(), mobileEquipmentErrorCode, input);
            }
            if (input.Length == 0)
            {
                this.LogIt("Failed. No answer from phone.");
                throw new CommException("No answer from phone.");
            }
            this.LogIt("Failed. Unexpected response.");
            this.LogIt("Received input:");
            this.LogItShow(input, "");
            throw new CommException("Unexpected response received from phone:" + Environment.NewLine + Environment.NewLine + input);
        }

        private void HandleRecvError(string input, string userMessage)
        {
            if (input.Length > 0)
            {
                this.LogIt("Failed. Receiving error. Data until error:");
                this.LogItShow(input, "");
            }
            else
            {
                this.LogIt("Failed. Receiving error.");
            }
            throw new CommException(userMessage);
        }

        private bool HandleUnsolicitedMessages(ref string bigInput)
        {
            MessageIndicationHandlers handlers = new MessageIndicationHandlers();
            if (handlers.IsUnsolicitedMessage(bigInput))
            {
                string str;
                this.LogItShow(bigInput, ">> ");
                IMessageIndicationObject obj2 = handlers.HandleUnsolicitedMessage(ref bigInput, out str);
                this.LogIt("Unsolicited message: " + str);
                this.OnMessageReceived(obj2);
                return true;
            }
            return false;
        }

        private bool IsCommError(string input)
        {
            return (input.IndexOf("\r\nERROR\r\n") >= 0);
        }

        private bool IsCommThreadRunning()
        {
            return ((_commThread != null) && _commThread.IsAlive);
        }

        /// <summary>
        /// Checks if there is a device connected and responsive.
        /// </summary>
        /// <returns>true if there is really a device connected and it responds to commands, false otherwise.</returns>
        /// <remarks>
        /// You can use this function after opening the port with <see cref="M:GsmComm.GsmCommunication.GsmPhone.Open" /> to verify that there is really a device connected
        /// before processding.
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.Open" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.IsOpen" />
        /// </remarks>
        public bool IsConnected()
        {
            lock (this)
            {
                return _connectionState;
            }
        }

        /// <summary>
        /// AT. Checks if there is a device connected and responsive.
        /// </summary>
        /// <returns>true if there is really a device connected and it responds to commands, false otherwise.</returns>
        /// <remarks>
        /// Bypasses the communication thread and does a direct send/receive.
        /// </remarks>
        private bool IsConnectedInternal()
        {
            lock (this)
            {
                try
                {
                    this.ExecCommandInternal("AT", "No phone connected.");
                }
                catch (Exception)
                {
                    return false;
                }
                return true;
            }
        }

        private bool IsMessageServiceError(string input)
        {
            return Regex.IsMatch(input, @"\r\n\+CMS ERROR: (\d+)\r\n");
        }

        /// <summary>
        /// Checks if there is a mobile equipment error message in the input string.
        /// </summary>
        /// <param name="input">The data received</param>
        /// <returns>true if there is a mobile equipment error message in the string, otherwiese false.</returns>
        private bool IsMobileEquipmentError(string input)
        {
            return Regex.IsMatch(input, @"\r\n\+CME ERROR: (\d+)\r\n");
        }

        /// <summary>
        /// Checks if the communication to the device is open.
        /// </summary>
        /// <returns>true if the port is open, false otherwise.</returns>
        /// <remarks><para>The port is open after a auccessful call to <see cref="M:GsmComm.GsmCommunication.GsmPhone.Open" /> and must be closed with
        /// <see cref="M:GsmComm.GsmCommunication.GsmPhone.Close" />.</para>
        /// <para>This function does not check if there is actually a device connected, use the <see cref="M:GsmComm.GsmCommunication.GsmPhone.IsConnected" />
        /// function for that.</para>
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.Open" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.Close" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.IsConnected" />
        /// </remarks>
        public bool IsOpen()
        {
            lock (this)
            {
                return ((_port != null) && _port.IsOpen);
            }
        }

        private bool IsSuccess(string input)
        {
            return (input.IndexOf("\r\nOK\r\n") >= 0);
        }

        /// <summary>
        /// AT+CMGL. Reads SMS messages from the current read/delete storage using the PDU mode.
        /// </summary>
        /// <param name="status">The message status</param>
        /// <returns>An array of <see cref="T:GsmComm.GsmCommunication.ShortMessage" /> objects representing the messages read.</returns>
        /// <remarks>Always switches to PDU mode at the beginning.</remarks>
        public ShortMessageFromPhone[] ListMessages(PhoneMessageStatus status)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.ActivatePduMode();
                this.LogIt("Reading messages, requesting status \"" + status.ToString() + "\"...");
                string command = "AT+CMGL=" + ((int) status);
                string input = ((ICommunication) this).ExecAndReceiveMultiple(command);
                ArrayList list = new ArrayList();
                Regex regex = new Regex(@"\+CMGL: (\d+),(\d+),(\w*),(\d+)\r\n(\w+)");
                for (Match match = regex.Match(input); match.Success; match = match.NextMatch())
                {
                    int index = int.Parse(match.Groups[1].Value);
                    int num2 = int.Parse(match.Groups[2].Value);
                    string alpha = match.Groups[3].Value;
                    int length = int.Parse(match.Groups[4].Value);
                    string data = match.Groups[5].Value;
                    this.LogIt("index=" + index.ToString() + ", stat=" + num2.ToString() + ", alpha=\"" + alpha + "\", length=" + length.ToString());
                    ShortMessageFromPhone phone = new ShortMessageFromPhone(index, num2, alpha, length, data);
                    list.Add(phone);
                }
                this.LogIt(list.Count.ToString() + " message(s) read.");
                ShortMessageFromPhone[] array = new ShortMessageFromPhone[list.Count];
                list.CopyTo(array, 0);
                return array;
            }
        }

        /// <summary>
        /// Lists the network operators detected by the phone.
        /// </summary>
        /// <returns>An array of <see cref="T:GsmComm.GsmCommunication.OperatorInfo2" /> objects containing the data of each operator.</returns>
        /// <remarks>If you want to determine the current operator, use the <see cref="M:GsmComm.GsmCommunication.GsmPhone.GetCurrentOperator" /> method.</remarks>
        public OperatorInfo2[] ListOperators()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Listing operators...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+COPS=?");
                ArrayList list = new ArrayList();
                if (!Regex.IsMatch(input, @"\+COPS: .*"))
                {
                    this.HandleCommError(input);
                    return null;
                }
                Regex regex = new Regex("\\((\\d+),(?:\"([^\\(\\)\\,]+)\")?,(?:\"([^\\(\\)\\,]+)\")?,(?:\"(\\d+)\")?(?:,([^\\(\\)\\,]+))?\\)");
                for (Match match = regex.Match(input); match.Success; match = match.NextMatch())
                {
                    int num = int.Parse(match.Groups[1].Value);
                    string longAlphanumeric = match.Groups[2].Value;
                    string shortAlphanumeric = match.Groups[3].Value;
                    string numeric = match.Groups[4].Value;
                    string accessTechnology = match.Groups[5].Value;
                    this.LogIt("stat={0}, longAlpha=\"{1}\", shortAlpha=\"{2}\", numeric=\"{3}\", act=\"{4}\"", new object[] { num, longAlphanumeric, shortAlphanumeric, numeric, accessTechnology });
                    if (!Enum.IsDefined(typeof(OperatorStatus), num))
                    {
                        throw new CommException("Unknown operator status " + num.ToString() + ".", input);
                    }
                    OperatorStatus status = (OperatorStatus) Enum.Parse(typeof(OperatorStatus), num.ToString());
                    OperatorInfo2 info = new OperatorInfo2(status, longAlphanumeric, shortAlphanumeric, numeric, accessTechnology);
                    list.Add(info);
                }
                this.LogIt(list.Count.ToString() + " operator(s) enumerated.");
                OperatorInfo2[] array = new OperatorInfo2[list.Count];
                list.CopyTo(array, 0);
                return array;
            }
        }

        private void LogIt(string text)
        {
            if ((this.LoglineAdded != null) && _logEnabled)
            {
                string str = string.Format("{0} [gsmphone] {1}", DateTime.Now.ToString("HH:mm:ss.fff"), text);
                lock (_logQueue)
                {
                    _logQueue.Enqueue(str);
                }
            }
        }

        private void LogIt(string text, params object[] args)
        {
            if ((LoglineAdded != null) && _logEnabled)
            {
                string str = string.Format(text, args);
                string str2 = string.Format("{0} [gsmphone] {1}", DateTime.Now.ToString("HH:mm:ss.fff"), str);
                lock (_logQueue)
                {
                    _logQueue.Enqueue(str2);
                }
            }
        }

        private void LogItShow(string data, string prefix)
        {
            this.LogItShow(data, prefix, false);
        }

        private void LogItShow(string data, string prefix, bool hideEmptyLines)
        {
            string[] strArray = this.SplitLines(data);
            if (strArray.Length != 0)
            {
                bool flag = false;
                for (int i = 0; i < strArray.Length; i++)
                {
                    if (!hideEmptyLines || (hideEmptyLines && (strArray[i].Length > 0)))
                    {
                        if (!flag)
                        {
                            this.LogIt(prefix + strArray[i]);
                            flag = true;
                        }
                        else
                        {
                            this.LogIt(strArray[i].PadLeft(strArray[i].Length + prefix.Length, ' '));
                        }
                    }
                }
            }
        }

        private void LogMemoryStatus(MemoryStatus status)
        {
            int num = (status.Total > 0) ? ((int) ((((double) status.Used) / ((double) status.Total)) * 100.0)) : 0;
            this.LogIt(string.Format("Memory status: {0}/{1} ({2}% used)", status.Used, status.Total, num));
        }

        private void LogThread()
        {
            _logQueue.Clear();
            LogIt("Log thread started.");
            while (!_terminateLogEvent.WaitOne(100, false))
            {
                while (DispatchLog())
                {
                }
            }
            LogIt("Log thread is terminating.");
            _logQueue.Clear();
        }

        private static string MakeArrayString(string[] array)
        {
            StringBuilder builder = new StringBuilder();
            foreach (string str in array)
            {
                if (builder.Length > 0)
                    builder.Append(", ");
                builder.Append(str);
            }
            return builder.ToString();
        }

        private static string MakeQueueString(Queue queue)
        {
            StringBuilder builder = new StringBuilder();
            lock (queue.SyncRoot)
            {
                foreach (string str in queue)
                {
                    builder.Append(str);
                }
            }
            return builder.ToString();
        }

        private static PinStatus MapPinStatusStringToStatus(string s)
        {
            PinStatus status;
            Dictionary<string, PinStatus> dictionary = new Dictionary<string, PinStatus>();
            dictionary["READY"] = PinStatus.Ready;
            dictionary["SIM PIN"] = PinStatus.SimPin;
            dictionary["SIM PUK"] = PinStatus.SimPuk;
            dictionary["PH-SIM PIN"] = PinStatus.PhoneToSimPin;
            dictionary["PH-FSIM PIN"] = PinStatus.PhoneToFirstSimPin;
            dictionary["PH-FSIM PUK"] = PinStatus.PhoneToFirstSimPuk;
            dictionary["SIM PIN2"] = PinStatus.SimPin2;
            dictionary["SIM PUK2"] = PinStatus.SimPuk2;
            dictionary["PH-NET PIN"] = PinStatus.PhoneToNetworkPin;
            dictionary["PH-NET PUK"] = PinStatus.PhoneToNetworkPuk;
            dictionary["PH-NETSUB PIN"] = PinStatus.PhoneToNetworkSubsetPin;
            dictionary["PH-NETSUB PUK"] = PinStatus.PhoneToNetworkSubsetPuk;
            dictionary["PH-SP PIN"] = PinStatus.PhoneToServiceProviderPin;
            dictionary["PH-SP PUK"] = PinStatus.PhoneToServiceProviderPuk;
            dictionary["PH-CORP PIN"] = PinStatus.PhoneToCorporatePin;
            dictionary["PH-CORP PUK"] = PinStatus.PhoneToCorporatePuk;
            if (!dictionary.TryGetValue(s, out status))
            {
                throw new CommException("Unknown PIN status \"" + s + "\".");
            }
            return status;
        }

        private void OnMessageReceived(IMessageIndicationObject obj)
        {
            if (this.MessageReceived != null)
            {
                this.LogIt("Firing async MessageReceived event.");
                MessageReceivedEventArgs e = new MessageReceivedEventArgs(obj);
                this.MessageReceived.BeginInvoke(this, e, new System.AsyncCallback(this.AsyncCallback), null);
            }
            else
            {
                this.LogIt("Warning: No event handlers for MessageReceived event, message is ignored.");
            }
        }

        private void OnPhoneConnected()
        {
            if (this.PhoneConnected != null)
            {
                this.LogIt("Firing async PhoneConnected event.");
                this.PhoneConnected.BeginInvoke(this, EventArgs.Empty, new System.AsyncCallback(this.AsyncCallback), null);
            }
        }

        private void OnPhoneDisconnected()
        {
            if (this.PhoneDisconnected != null)
            {
                this.LogIt("Firing async PhoneDisconnected event.");
                this.PhoneDisconnected.BeginInvoke(this, EventArgs.Empty, new System.AsyncCallback(this.AsyncCallback), null);
            }
        }

        private void OnReceiveComplete(int bytesReceived)
        {
            if (this.ReceiveComplete != null)
            {
                ProgressEventArgs e = new ProgressEventArgs(bytesReceived);
                this.ReceiveComplete.BeginInvoke(this, e, new System.AsyncCallback(this.AsyncCallback), null);
            }
        }

        private void OnReceiveProgress(int bytesReceived)
        {
            if (this.ReceiveProgress != null)
            {
                ProgressEventArgs e = new ProgressEventArgs(bytesReceived);
                this.ReceiveProgress.BeginInvoke(this, e, new System.AsyncCallback(this.AsyncCallback), null);
            }
        }

        /// <summary>
        /// Opens the connection to the device.
        /// </summary>
        /// <remarks>You can check the current connection state with the <see cref="M:GsmComm.GsmCommunication.GsmPhone.IsOpen" /> method.
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.IsOpen" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.Close" />
        /// </remarks>
        /// <exception cref="T:System.InvalidOperationException">Connection to device already open.</exception>
        /// <exception cref="T:GsmComm.GsmCommunication.CommException">Unable to open the port.</exception>
        public void Open()
        {
            lock (this)
            {
                if (IsOpen())
                    throw new InvalidOperationException("Port already open.");
                CreateLogThread();
                try
                {
                    OpenPort(_portNum, _baudRate, _timeout);
                    LogIt("Port COM" + _portNum.ToString() + " now open.");
                }
                catch (Exception exception)
                {
                    string text = "Unable to open port COM" + _portNum.ToString() + ": " + exception.Message;
                    this.LogIt(text);
                    this.TerminateLogThread();
                    throw new CommException(text, exception);
                }
                this.CheckConnection();
                try
                {
                    this.CreateCommThread();
                }
                catch (Exception exception2)
                {
                    this.ClosePort();
                    this.TerminateLogThread();
                    throw new CommException("Unable to create communication thread.", exception2);
                }
            }
        }

        private void OpenPort(int portNum, int baudRate, int timeout)
        {
            LogIt("Initializing serial connection...");
            LogIt("  Port = COM" + portNum.ToString());
            LogIt("  Baud rate = " + baudRate.ToString());
            LogIt("  Timeout = " + timeout.ToString());
            if (_port == null)
            {
                _port = new SerialPort();
            }
            else if (_port.IsOpen)
            {
                throw new CommException("Port already open.");
            }
            string str = "COM" + portNum.ToString();
            string[] portNames = SerialPort.GetPortNames();
            bool flag = false;
            foreach (string str2 in portNames)
            {
                if (str2 == str)
                {
                    flag = true;
                    break;
                }
            }
            if (!flag)
            {
                LogIt("Invalid connection settings. Port COM" + portNum.ToString() + " does not exist on this system.");
                throw new CommException("Invalid connection settings. Port COM" + portNum.ToString() + " does not exist on this system.");
            }
            try
            {
                _port.PortName = str;
                _port.BaudRate = baudRate;
                _port.DataBits = 8;
                _port.StopBits = StopBits.One;
                _port.Parity = Parity.None;
                _port.ReadTimeout = timeout;
                _port.WriteTimeout = timeout;
                _port.Encoding = Encoding.GetEncoding(0x4e4);
                _port.DataReceived += port_DataReceived;
                _port.Open();
                _port.DtrEnable = true;
                _port.RtsEnable = true;
            }
            catch (Exception exception)
            {
                LogIt("Failed. Error: " + exception.Message);
                if (_port.IsOpen)
                    _port.Close();
                throw;
            }
        }

        /// <summary>
        /// Parses the memory status response of the CPMS command.
        /// </summary>
        /// <param name="input">A response to the +CPMS set command</param>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.MessageMemoryStatus" /> object containing the status information of the storages.</returns>
        /// <remarks>
        /// <para>Data for the ReceiveStorage (mem3) is null if there is no information available about it.</para>
        /// </remarks>
        private MessageMemoryStatus ParseMessageMemoryStatus(string input)
        {
            MessageMemoryStatus status;
            Match match = new Regex(@"\+CPMS: (\d+),(\d+),(\d+),(\d+)(?:,(\d+),(\d+))?").Match(input);
            if (match.Success)
            {
                status = new MessageMemoryStatus();
                int used = int.Parse(match.Groups[1].Value);
                int total = int.Parse(match.Groups[2].Value);
                int num3 = int.Parse(match.Groups[3].Value);
                int num4 = int.Parse(match.Groups[4].Value);
                status.ReadStorage = new MemoryStatus(used, total);
                status.WriteStorage = new MemoryStatus(num3, num4);
                if ((match.Groups[5].Captures.Count > 0) && (match.Groups[6].Captures.Count > 0))
                {
                    int num5 = int.Parse(match.Groups[5].Value);
                    int num6 = int.Parse(match.Groups[6].Value);
                    status.ReceiveStorage = new MemoryStatus(num5, num6);
                }
                return status;
            }
            status = this.TryParseMessageMemoryStatus2(input);
            if (status == null)
            {
                this.HandleCommError(input);
                return null;
            }
            return status;
        }

        /// <summary>
        /// Parses the memory status response of the CPBS command.
        /// </summary>
        /// <param name="input">A response to the +CPBS set command</param>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.MemoryStatus" /> object containing the memory details.</returns>
        private MemoryStatusWithStorage ParsePhonebookMemoryStatus(string input)
        {
            Match match = new Regex("\\+CPBS: \"(\\w+)\",(\\d+),(\\d+)").Match(input);
            if (match.Success)
            {
                string storage = match.Groups[1].Value;
                int used = int.Parse(match.Groups[2].Value);
                return new MemoryStatusWithStorage(storage, used, int.Parse(match.Groups[3].Value));
            }
            this.HandleCommError(input);
            return null;
        }

        private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (e.EventType == SerialData.Chars)
            {
                _receiveNow.Set();
            }
        }

        /// <summary>
        /// AT+CMGR. Reads a single SMS message from the current read/delete storage using the PDU mode.
        /// </summary>
        /// <param name="index">The index of the message to read.</param>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.ShortMessage" /> object containing the message at the index specified.</returns>
        /// <remarks>Always switches to PDU mode at the beginning.</remarks>
        public ShortMessageFromPhone ReadMessage(int index)
        {
            ShortMessageFromPhone phone2;
            lock (this)
            {
                this.VerifyValidConnection();
                this.ActivatePduMode();
                this.LogIt("Reading message from index " + index.ToString() + "...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CMGR=" + index.ToString());
                Match match = new Regex(@"\+CMGR: (\d+),(\w*),(\d+)\r\n(\w+)").Match(input);
                if (match.Success)
                {
                    int status = int.Parse(match.Groups[1].Value);
                    string alpha = match.Groups[2].Value;
                    int length = int.Parse(match.Groups[3].Value);
                    string data = match.Groups[4].Value;
                    this.LogIt("stat=" + status.ToString() + ", alpha=\"" + alpha + "\", length=" + length.ToString());
                    ShortMessageFromPhone phone = new ShortMessageFromPhone(index, status, alpha, length, data);
                    phone2 = phone;
                }
                else
                {
                    this.HandleCommError(input);
                    throw new CommException("Unexpected response.", input);
                }
            }
            return phone2;
        }

        /// <summary>
        /// Gets the entire phonebook of the currently selected phonebook storage.
        /// </summary>
        /// <returns>An array of <see cref="T:GsmComm.GsmCommunication.PhonebookEntry" /> objects.</returns>
        public PhonebookEntry[] ReadPhonebookEntries()
        {
            lock (this)
            {
                int num;
                int num2;
                int num3;
                int num4;
                this.GetPhonebookSize(out num, out num2, out num3, out num4);
                return this.ReadPhonebookEntries(num, num2);
            }
        }

        /// <summary>
        /// AT+CPBR. Gets the specified range of phonebook entries.
        /// </summary>
        /// <param name="lowerBound">The first entry to get</param>
        /// <param name="upperBound">The last entry to get</param>
        /// <returns>An array of <see cref="T:GsmComm.GsmCommunication.PhonebookEntry" /> objects</returns>
        public PhonebookEntry[] ReadPhonebookEntries(int lowerBound, int upperBound)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt(string.Concat(new object[] { "Getting phonebook from index ", lowerBound, " to ", upperBound, "..." }));
                string command = "AT+CPBR=" + lowerBound.ToString() + "," + upperBound.ToString();
                string input = ((ICommunication) this).ExecAndReceiveMultiple(command);
                return this.DecodePhonebookStream(input, "+CPBR: ");
            }
        }

        /// <summary>
        /// Receives raw data as a string.
        /// </summary>
        /// <param name="input">Receives the data received.</param>
        /// <returns>true if data was received, otherwise false.</returns>
        private bool ReceiveInternal(out string input)
        {
            var builder = new StringBuilder();
            for (string str = _port.ReadExisting(); str.Length > 0; str = _port.ReadExisting())
                builder.Append(str);
            input = builder.ToString();
            return (input.Length > 0);
        }

        /// <summary>
        /// AT+GMI. Requests TA manufacturer identifaction.
        /// </summary>
        /// <returns>The product manufacturer identification.</returns>
        public string RequestTAManufacturer()
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Requesting TA manufacturer...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+GMI");
                string str2 = TrimLineBreaks(input);
                LogIt("Manufacturer = \"" + str2 + "\"");
                return str2;
            }
        }

        /// <summary>
        /// AT+GMM. Requests TA model identification.
        /// </summary>
        /// <returns>The product model identification.</returns>
        public string RequestTAModel()
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Requesting TA model...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+GMM");
                string str2 = this.TrimLineBreaks(input);
                this.LogIt("Model = \"" + str2 + "\"");
                return str2;
            }
        }

        /// <summary>
        /// AT+GMR. Requests TA revision identification.
        /// </summary>
        /// <returns>The product revision identifiaction.</returns>
        public string RequestTARevision()
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Requesting TA revision...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+GMR");
                string str2 = TrimLineBreaks(input);
                LogIt("Revision = \"" + str2 + "\"");
                return str2;
            }
        }

        /// <summary>
        /// AT+GSN. Requests TA serial number identifiaction
        /// </summary>
        /// <returns>The product serial number.</returns>
        public string RequestTASerialNumber()
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Requesting TA serial number...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+GSN");
                string str2 = TrimLineBreaks(input);
                LogIt("Serial Number = \"" + str2 + "\"");
                return str2;
            }
        }

        /// <summary>
        /// ATZ. Settings that are not stored in a profile will be reset to their factory defaults.
        /// </summary>
        public void ResetToDefaultConfig()
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Resetting to default configuration...");
                ((ICommunication) this).ExecAndReceiveMultiple("ATZ");
            }
        }

        /// <summary>
        /// AT+CSCS. Selects the character set.
        /// </summary>
        /// <param name="charset">The character set to use.</param>
        /// <remarks>This command informs the data card of which character set is used by the TE. The data card is
        /// then able to convert character strings correctly between TE and ME character sets. When the data card-TE
        /// interface is set to 8-bit operation and the TE uses a 7-bit alphabet, the highest bit shall be set to
        /// zero. This setting affects text mode SMS data and alpha fields in the phone book memory. If the ME is
        /// using the GSM default alphabet, its characters shall be padded with the 8th bit (zero) before
        /// converting them to hexadecimal numbers (that is, a 7-bit alphabet is not packed in the SMS-style
        /// packing).
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.GetCurrentCharacterSet" />
        /// <seealso cref="M:GsmComm.GsmCommunication.GsmPhone.GetSupportedCharacterSets" />
        /// </remarks>
        public void SelectCharacterSet(string charset)
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Selecting character set \"" + charset + "\"...");
                ((ICommunication) this).ExecAndReceiveMultiple("AT+CSCS=\"" + charset + "\"");
            }
        }

        /// <summary>
        /// AT+CSMS. Selects the specified messaging service.
        /// </summary>
        /// <param name="service">The service to select. Specifies the compatibility level of the SMS AT commands.
        /// The requirement of service setting 1 depends on specific commands.
        /// </param>
        /// <param name="mt">ME supports mobile terminated messages</param>
        /// <param name="mo">ME supports mobile originated messages</param>
        /// <param name="bm">ME supports broadcast type messages</param>
        public void SelectMessageService(int service, out int mt, out int mo, out int bm)
        {
            lock (this)
            {
                VerifyValidConnection();
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CSMS=" + service.ToString());
                Match match = new Regex(@"\+CSMS: (\d+),(\d+),(\d+)").Match(input);
                if (!match.Success)
                {
                    HandleCommError(input);
                    throw new CommException("Unexpected response.", input);
                }
                mt = int.Parse(match.Groups[1].Value);
                mo = int.Parse(match.Groups[2].Value);
                bm = int.Parse(match.Groups[3].Value);
            }
        }

        /// <summary>
        /// AT+CPBS. Selects the storage to use for phonebook operations.
        /// </summary>
        /// <param name="storage">The storage to use.</param>
        /// <returns>The memory status of the selected storage.</returns>
        public void SelectPhonebookStorage(string storage)
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Selecting \"" + storage + "\" phonebook memory...");
                ((ICommunication) this).ExecAndReceiveMultiple("AT+CPBS=\"" + storage + "\"");
            }
        }

        /// <summary>
        /// AT+CPMS. Selects the storage to use for read and delete operations.
        /// </summary>
        /// <param name="storage">The storage to use</param>
        /// <returns>The memory status of the selected storage.</returns>
        /// <remarks>This selects the preferred message storage "mem1" on the device.</remarks>
        public MemoryStatus SelectReadStorage(string storage)
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Selecting \"" + storage + "\" as read storage...");
                string input = ((ICommunication) this).ExecAndReceiveMultiple("AT+CPMS=\"" + storage + "\"");
                MemoryStatus readStorage = this.ParseMessageMemoryStatus(input).ReadStorage;
                LogMemoryStatus(readStorage);
                return readStorage;
            }
        }

        /// <summary>
        /// AT+CPMS. Selects the storage to use for write and send operations.
        /// </summary>
        /// <param name="storage">The storage to use</param>
        /// <returns>The memory status of the selected storage</returns>
        /// <remarks>This selects the preferred message storage "mem2" on the device. Additionaly, the "mem1" storage
        /// is also set to the same storage since the read storage must also be set when selecting the 
        /// write storage.</remarks>
        public MemoryStatus SelectWriteStorage(string storage)
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Selecting \"" + storage + "\" as write storage...");
                string str = storage;
                string str2 = storage;
                string command = "AT+CPMS=\"" + str + "\",\"" + str2 + "\"";
                string input = ((ICommunication) this).ExecAndReceiveMultiple(command);
                MemoryStatus writeStorage = ParseMessageMemoryStatus(input).WriteStorage;
                LogMemoryStatus(writeStorage);
                return writeStorage;
            }
        }

        /// <summary>
        /// Sends raw data as a string.
        /// </summary>
        /// <param name="output">The data to send.</param>
        /// <param name="logIt">Specifies if the data sent should be logged.</param>
        /// <remarks>Send and receive buffers are cleared before sending.</remarks>
        private void SendInternal(string output, bool logIt)
        {
            if (logIt)
            {
                this.LogItShow(output, "<< ");
            }
            _port.DiscardOutBuffer();
            _port.DiscardInBuffer();
            _port.Write(output);
        }

        /// <summary>
        /// AT+CMGS. Sends an SMS message using PDU mode.
        /// </summary>
        /// <param name="pdu">The PDU stream to send</param>
        /// <param name="actualLength">The actual length of the PDU (not counting the SMSC data)</param>
        /// <returns>The message reference</returns>
        /// <remarks>Always switches to PDU mode at the beginning.</remarks>
        public byte SendMessage(string pdu, int actualLength)
        {
            lock (this)
            {
                VerifyValidConnection();
                ActivatePduMode();
                LogIt("Sending message...");
                ((ICommunication) this).ExecAndReceiveAnything("AT+CMGS=" + actualLength, @"\r\n> ");
                ((ICommunication) this).Send(pdu + '\x001a');
                string input = ((ICommunication) this).ReceiveMultiple();
                byte num = 0;
                Match match = new Regex(@"\+CMGS: (\d+)(,(\w+))*").Match(input);
                if (match.Success)
                {
                    num = (byte) int.Parse(match.Groups[1].Value);
                    LogIt("Message reference = " + num);
                }
                else
                {
                    LogIt("Warning: Could not get message reference. Answer received was: \"" + input + "\"");
                }
                return num;
            }
        }

        /// <summary>
        /// AT+CNMI. Selects the procedure for indicating new messages received from the network.
        /// </summary>
        /// <param name="settings">A <see cref="T:GsmComm.GsmCommunication.MessageIndicationSettings" /> structure containing the
        /// detailed settings.</param>
        /// <remarks>The function switches to the PDU mode before setting the notifications. This
        /// causes all short messages, that are directly routed, to be presented in PDU mode. If the mode
        /// is changed (such as a switch to the text mode), all indications (containing a message) following the
        /// change are sent in the new mode.
        /// </remarks>
        public void SetMessageIndications(MessageIndicationSettings settings)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.ActivatePduMode();
                this.LogIt("Setting message notifications...");
                string command = string.Format("AT+CNMI={0},{1},{2},{3},{4}", new object[] { settings.Mode, settings.DeliverStyle, settings.CellBroadcastStyle, settings.StatusReportStyle, settings.BufferSetting });
                ((ICommunication) this).ExecAndReceiveMultiple(command);
            }
        }

        /// <summary>
        /// AT+CMMS. Sets the SMS batch mode.
        /// </summary>
        /// <param name="mode">The new mode to set.</param>
        public void SetMoreMessagesToSend(MoreMessagesMode mode)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Setting more messages mode {0}...", new object[] { mode });
                string command = string.Format("AT+CMMS={0}", (int) mode);
                ((ICommunication) this).ExecAndReceiveMultiple(command);
            }
        }

        /// <summary>
        /// AT+CSCA. Sets the SMS Service Center Address.
        /// </summary>
        /// <param name="data">An <see cref="T:GsmComm.GsmCommunication.AddressData" /> object containing the new address</param>
        /// <remarks>This command changes the SMSC address, through which SMS messages are transmitted.
        /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is
        /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals
        /// zero.</remarks>
        public void SetSmscAddress(AddressData data)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.LogIt("Setting SMSC address to \"{0}\", type {1}...", new object[] { data.Address, data.TypeOfAddress.ToString() });
                string command = string.Format("AT+CSCA=\"{0}\",{1}", data.Address, data.TypeOfAddress.ToString());
                ((ICommunication) this).ExecAndReceiveMultiple(command);
            }
        }

        /// <summary>
        /// AT+CSCA. Sets the SMS Service Center Address.
        /// </summary>
        /// <param name="address">The new SMSC address</param>
        /// <remarks>This command changes the SMSC address, through which SMS messages are transmitted.
        /// In text mode, setting is used by send and write commands. In PDU mode, setting is used by the same
        /// commands, but only when the length of the SMSC address coded into the PDU data equals zero.</remarks>
        public void SetSmscAddress(string address)
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Setting SMSC address to \"{0}\"...", new object[] { address });
                string command = string.Format("AT+CSCA=\"{0}\"", address);
                ((ICommunication) this).ExecAndReceiveMultiple(command);
            }
        }

        private string[] SplitLines(string data)
        {
            data = data.Replace('\r' + '\n'.ToString(), '\r'.ToString());
            return data.Split(new char[] { '\r' });
        }

        private void TerminateCommThread()
        {
            if (!IsCommThreadRunning()) return;
            _terminateThread.Set();
            try
            {
                if (!_commThread.Join(0x2710))
                {
                    LogIt("Warning: Communication thread did not exit within the timeout, aborting thread.");
                    _commThread.Abort();
                }
            }
            catch (Exception exception)
            {
                LogIt("Error while terminating comm thread: " + exception.Message);
            }
            _commThread = null;
        }

        private void TerminateLogThread()
        {
            if ((_logThread != null) && _logThread.IsAlive)
            {
                _terminateLogEvent.Set();
                try
                {
                    if (!_logThread.Join(0x2710))
                    {
                        LogIt("Warning: Log thread did not exit within the timeout, aborting thread.");
                        _logThread.Abort();
                    }
                }
                catch (Exception exception)
                {
                    LogIt("Error while terminating log thread: " + exception.Message);
                }
                _logThread = null;
            }
        }

        /// <summary>
        /// Removes all line termination characters from a string.
        /// </summary>
        /// <param name="input">The string to trim.</param>
        /// <returns>The modified string.</returns>
        private string TrimLineBreaks(string input)
        {
            return input.Trim(new char[] { '\r', '\n' });
        }

        /// <summary>
        /// Tries to parse an alternative memory status response of the CPMS command.
        /// </summary>
        /// <param name="input">A response to the +CPMS set command</param>
        /// <returns>A <see cref="T:GsmComm.GsmCommunication.MessageMemoryStatus" /> object containing the status information of the storages
        /// if successful, otherwise null.</returns>
        private MessageMemoryStatus TryParseMessageMemoryStatus2(string input)
        {
            MessageMemoryStatus status = null;
            Match match = new Regex("\\+CPMS: \"(\\w+)\",(\\d+),(\\d+),\"(\\w+)\",(\\d+),(\\d+)(?:,\"(\\w+)\",(\\d+),(\\d+))?").Match(input);
            if (match.Success)
            {
                status = new MessageMemoryStatus();
                string storage = match.Groups[1].Value;
                int used = int.Parse(match.Groups[2].Value);
                int total = int.Parse(match.Groups[3].Value);
                string str2 = match.Groups[4].Value;
                int num3 = int.Parse(match.Groups[5].Value);
                int num4 = int.Parse(match.Groups[6].Value);
                status.ReadStorage = new MemoryStatusWithStorage(storage, used, total);
                status.WriteStorage = new MemoryStatusWithStorage(str2, num3, num4);
                if (match.Groups[7].Captures.Count > 0)
                {
                    string str3 = match.Groups[7].Value;
                    int num5 = int.Parse(match.Groups[8].Value);
                    int num6 = int.Parse(match.Groups[9].Value);
                    status.ReceiveStorage = new MemoryStatusWithStorage(str3, num5, num6);
                }
            }
            return status;
        }

        private void VerifyValidConnection()
        {
            if (!this.IsOpen())
            {
                this.LogIt("Port not open.");
                throw new InvalidOperationException("Port not open.");
            }
            if (!_connectionState)
            {
                this.LogIt("No phone connected.");
                throw new CommException("No phone connected.");
            }
        }

        /// <summary>
        /// AT+CMGW. Stores an SMS message in the current write/send storage using PDU mode.
        /// </summary>
        /// <param name="pdu">The message in PDU format</param>
        /// <param name="actualLength">The actual length of the PDU (not counting the SMSC data)</param>
        /// <returns>The index of the message. If the index could not be retrieved, zero is returned.</returns>
        /// <remarks><para>The message is saved with a status predefined by the phone</para>
        /// <para>This function always switches to the PDU mode at the beginning.</para></remarks>
        public int WriteMessageToMemory(string pdu, int actualLength)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.ActivatePduMode();
                this.LogIt("Writing message to memory...");
                ((ICommunication) this).ExecAndReceiveAnything("AT+CMGW=" + actualLength.ToString(), @"\r\n> ");
                return this.WriteMessageToMemoryPart2(pdu);
            }
        }

        /// <summary>
        /// AT+CMGW. Stores an SMS message in the current write/send storage using PDU mode.
        /// </summary>
        /// <param name="pdu">The message in PDU format</param>
        /// <param name="actualLength">The actual length of the PDU (not counting the SMSC data)</param>
        /// <param name="status">The status that the message should get when saved.</param>
        /// <returns>The index of the message. If the index could not be retrieved, zero is returned.</returns>
        /// <remarks><para>The message is saved with a status predefined by the phone</para>
        /// <para>This function always switches to the PDU mode at the beginning.</para></remarks>
        public int WriteMessageToMemory(string pdu, int actualLength, int status)
        {
            lock (this)
            {
                this.VerifyValidConnection();
                this.ActivatePduMode();
                this.LogIt("Writing message to memory...");
                ((ICommunication) this).ExecAndReceiveAnything("AT+CMGW=" + actualLength.ToString() + "," + status.ToString(), @"\r\n> ");
                return this.WriteMessageToMemoryPart2(pdu);
            }
        }

        private int WriteMessageToMemoryPart2(string pdu)
        {
            ((ICommunication) this).Send(pdu + '\x001a');
            string input = ((ICommunication) this).ReceiveMultiple();
            int num = 0;
            Match match = new Regex(@"\+CMGW: (\d+)").Match(input);
            if (match.Success)
            {
                num = int.Parse(match.Groups[1].Value);
                LogIt("Message index = " + num);
                return num;
            }
            LogIt("Warning: Could not get message index. Answer received was: \"" + input + "\"");
            return num;
        }

        /// <summary>
        /// AT+CPBW. Creates a new phonebook entry.
        /// </summary>
        /// <param name="entry">The entry to write.</param>
        /// <remarks>The <see cref="P:GsmComm.GsmCommunication.PhonebookEntry.Index" /> property of the entry is ignored, 
        /// the entry is always saved in the first free location. All other properties must be set
        /// correctly.</remarks>
        public void WritePhonebookEntry(PhonebookEntry entry)
        {
            lock (this)
            {
                VerifyValidConnection();
                LogIt("Writing phonebook entry...");
                string command = string.Concat(new object[] { "AT+CPBW=,\"", entry.Number, "\",", entry.Type, ",\"", entry.Text, "\"" });
                ((ICommunication) this).ExecAndReceiveMultiple(command);
            }
        }

        /// <summary>
        /// Gets the baud rate.
        /// </summary>
        public int BaudRate
        {
            get
            {
                return _baudRate;
            }
        }

        /// <summary>
        /// Gets or sets the delay in milliseconds between the checks to verify that the connection
        /// to the phone is still alive.
        /// </summary>
        public int ConnectionCheckDelay
        {
            get
            {
                return _commThreadCheckDelay;
            }
            set
            {
                int num = (value < 0x3e8) ? 0x3e8 : value;
                _commThreadCheckDelay = num;
            }
        }

        /// <summary>
        /// Gets the COM port.
        /// </summary>
        public int PortNumber
        {
            get
            {
                return _portNum;
            }
        }

        /// <summary>
        /// Gets the communication timeout.
        /// </summary>
        public int Timeout
        {
            get { return _timeout; }
        }

        /// <summary>
        /// The method that handles the GsmPhone.LoglineAdded event.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="text">The line text.</param>
        public delegate void LoglineAddedEventHandler(object sender, string text);
    }
}